package asdf.utils;

import org.apache.commons.codec.binary.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * Created by lichengwen on 2016/10/26.
 */
public class RSAUtils
{
    private final static int PAD_PKCS1_LENGTH = 11;
    private final static String ALGORITHM = "RSA";
    
    /**
     * decode with base64
     */
    public static byte[] toBytes(String s)
    {
        return Base64.decodeBase64(s);
    }
    
    /**
     * encode with base64
     */
    public static String toString(byte[] b)
    {
        return Base64.encodeBase64String(b);
    }
    
    private static PrivateKey buildPrivateKey(String sPrivateKey)
    {
        try {
            PrivateKey key = KeyFactory.getInstance(ALGORITHM)
                    .generatePrivate(
                            new PKCS8EncodedKeySpec(toBytes(sPrivateKey)));
            System.out.println(key);
            return key;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    private static PublicKey buildPublicKey(String sPublicKey)
    {
        try {
            PublicKey key = KeyFactory.getInstance(ALGORITHM)
                    .generatePublic(
                            new X509EncodedKeySpec(toBytes(sPublicKey)));
            System.out.println(key.getEncoded().length);
            return key;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * 用私钥对信息生成数字签名
     */
    public static String sign(byte[] data, String privateKey, String signatureAlgorithm)
    {
        try {
            Signature signature = Signature.getInstance(signatureAlgorithm);
            signature.initSign(buildPrivateKey(privateKey));
            signature.update(data);
            return toString(signature.sign());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * 校验数字签名
     */
    public static boolean checkSign(
            byte[] data, String publicKey, String sign, String signatureAlgorithm)
    {
        try {
            Signature signature = Signature.getInstance(signatureAlgorithm);
            signature.initVerify(buildPublicKey(publicKey));
            signature.update(data);
            return signature.verify(toBytes(sign));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * 解密
     *
     * @param isPrivateKey true for PrivateKey, false for PublicKey
     */
    public static byte[] decrypt(byte[] data, String key, boolean isPrivateKey)
    {
        try {
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE,
                        (isPrivateKey ? buildPrivateKey(key) : buildPublicKey(key)));
            return doCipher(data, cipher.getOutputSize(data.length), cipher);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * 加密
     *
     * @param isPublicKey true for PublicKey, false for PrivateKey
     */
    public static byte[] encrypt(byte[] data, String key, boolean isPublicKey)
    {
        try {
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE,
                        (isPublicKey ? buildPublicKey(key) : buildPrivateKey(key)));
            return doCipher(data, cipher.getOutputSize(data.length) - PAD_PKCS1_LENGTH, cipher);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    private static byte[] doCipher(byte[] data, int maxLen, Cipher cipher)
            throws IllegalBlockSizeException, BadPaddingException
    {
        if (data.length <= maxLen) {
            return cipher.doFinal(data);
        } else {
            ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
            for (int i = 0; i < data.length; i = i + maxLen) {
                byte[] snippet;
                if (data.length < i + maxLen) {
                    snippet = new byte[data.length - i];
                } else {
                    snippet = new byte[maxLen];
                }
                System.arraycopy(data, i, snippet, 0, snippet.length);
                byte[] encryptedSnippet =  cipher.doFinal(snippet);
                byteStream.write(encryptedSnippet, 0, encryptedSnippet.length);
            }
            return byteStream.toByteArray();
        }
    }
    
    public static void printAlgorithms()
    {
        for (Provider provider : Security.getProviders()) {
            System.out.println("Provider: " + provider.getName());
            for (Provider.Service service : provider.getServices()) {
                System.out.println("  Algorithm: " + service.getAlgorithm());
            }
        }
        
    }
    
    public static void main(String[] args)
    {
        String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC2hibHm3SYcINhqDZxyGb9TUpDwamO2RZ8uNUVijiVLShXPSjZu3p7QiSx4QkOSrVJzUW1Nqdks8cq/IKWQQ32eHIoJzw9zCcIZHZ52dz4sG6jbXT3pUdgzRlA0g2GlEpTGrS9ECQjuCeqcI5YtGfW/SOm0rg4Y6jz/O1QXpZI3QIDAQAB";
        String privateKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALaGJsebdJhwg2GoNnHIZv1NSkPBqY7ZFny41RWKOJUtKFc9KNm7entCJLHhCQ5KtUnNRbU2p2Szxyr8gpZBDfZ4cignPD3MJwhkdnnZ3PiwbqNtdPelR2DNGUDSDYaUSlMatL0QJCO4J6pwjli0Z9b9I6bSuDhjqPP87VBelkjdAgMBAAECgYAE54vpNysl4XgWSq/T1N+3lLkXIwAMX5b0GP9YydMFZyY7WeBcIWaEI13bDcHxiV5fsk1aNjK/SPlkFU7o/KyEVE/yeGVkKgtYf6ZHkkSvJAb5EqPHNK4mliv6xHHwPFhksj1yQ+v1ClHh8bErMsXhjGgiN4m0RoYNGuPw+qnk/QJBANtzZiF1Xz5vRZ60uVBbTVgZRIUULOWVcoULBH00/MxuQwE5R38iCIl2bm2Fzt+/g1+boavt0GeMnQQQQnXCLw8CQQDU7FPH3laRRyn8vVvcL569WbekEy+R/xWhYIZozfjy8ZJn9l7VvJ+4qViRC5tt3jZ/xrrqCWEehDB1ASAjbolTAkA0nyhXT5DGJOJ52vYw2aPjQzPWAW9+oeZzFqA98c3Iq4vvqC+Hwb5wT/DM3HAt4Pt8mpIa6wy1N9eS2N9G44hrAkBlqHfPw23dava2/smu9OV7TiyJn8dkkz+GjwldXInxpDjcfhqFPcucZO4uQjZxXFj05LM1Lkk6Qq7IDkOD5zCVAkEAlJbd6kXotN6RaMx0PWzY/xmL0Tx//zNQEJCAFXU4sr8RuAZirY9PM4UeaKQrlVYDvXcm46DNZpUNtrqDhfeOLg==";
        
        String s = "在计算机科学中，信号是Unix、类Unix以及其他POSIX兼容的操作系统中进程间通讯的一种有限制的方式。\n" +
                   "<?xml version=\"1.0\" encoding=\"GBK\"?><Message>\n" +
                     "<applicationNo>f40b67a33b6748edb733079f99b78a8f</applicationNo><status>0</status><errorMessage" +
                     ">成功</errorMessage><investorName>西方失败</investorName><certType>0</certType><certificateNo" +
                     ">120105199001010338</certificateNo><bankCode>002</bankCode><needCheckMobileNo>1" +
                     "</needCheckMobileNo><needConfirm>0</needConfirm><sign>EFUgcFGdf82cnHp40uz4u2Jd2E9" +
                     "/XYa2aXfjf5xZECqsJ+mGFLodWjfI6Vocub1hnxmdHXT8UD7FXCBPyN3sD26EiXghTgmliygj3JdMuN9w0" +
                     "+SRCIHLkO2uq1P+SLkS9dOd6orLsdLIzJvyWOYZQ7+c1NbeHoz5eoIjBEvDNEI=</sign></Message>\n" +
                     "在计算机科学中，信号是Unix、类Unix以及其他POSIX兼容的操作系统中进程间通讯的一种有限制的方式。" +
                     "它是一种异步的通知机制，用来提醒进程一个事件已经发生";
        byte[] e = encrypt(s.getBytes(), publicKey, true);
        System.out.println(new String(e));
        byte[] d = decrypt(e, privateKey, true);
        System.out.println(new String(d));
    }
}
